package no.sachmuld.methodcombination.api.adapter;

import no.sachmuld.methodcombination.impl.AbstractMethodInvocation;
import no.sachmuld.methodcombination.api.BehaviourChain;
import no.sachmuld.methodcombination.api.CrosscuttingBehaviour;
import no.sachmuld.methodcombination.api.MethodInvocation;
import org.apache.log4j.Logger;

import java.lang.reflect.Method;

/**
 *
 */
public abstract class Crosscutting<B extends CrosscuttingBehaviour, R> {

    private BehaviourChain<B> chain;

    public Crosscutting(BehaviourChain<B> chain) {
        this.chain = chain;
    }

    public R execute() {

        R result = new AbstractMethodInvocation<B, R>(chain.bottomUp(), null, null, null) {
            @Override
            protected R executeBehaviour(B behaviour) {
                return (R) behaviour.around(this);
            }

            @Override
            protected R atEnd() {
                return executeBeforePrimaryAfter();
            }
        }.execute();

        return result;
    }

    private R executeBeforePrimaryAfter() {

        MethodInvocation auxilliaryMethodInvocation = new AuxilliaryMethodInvocation();

        for (B behaviour : chain.bottomUp()) {
            behaviour.before(auxilliaryMethodInvocation);
        }

        R result = new AbstractMethodInvocation<B, R>(chain.bottomUp(), null, null, null) {
            @Override
            protected R executeBehaviour(B behaviour) {
                return executePrimary(behaviour, this);
            }
        }.execute();

        for (B behaviour : chain.topDown()) {
            behaviour.after(auxilliaryMethodInvocation);
        }

        return result;
    }

    protected abstract R executePrimary(B behaviour, MethodInvocation<R> methodInvocation);

    private static class AuxilliaryMethodInvocation implements MethodInvocation {

        private static Logger logger = Logger.getLogger(AuxilliaryMethodInvocation.class);

        private AuxilliaryMethodInvocation() {
        }

        @Override
        public Object proceed() {
            logger.warn("Calling proceed() in an auxilliary method has no effect");
            return null;
        }

        @Override
        public Object getThis() {
            return null;
        }

        @Override
        public Method getMethod() {
            return null;
        }

        @Override
        public Object[] getArguments() {
            return null;
        }
    }

}
